﻿using System;
using System.Collections.Generic;
using System.Dataflow;
using System.IO;
using System.Linq;
using Microsoft.M;
using Simp.CodeModel;
using Simp.CodeModel.CoreFunctions;
using Simp.CodeModel.Exceptions;
using Simp.CodeModel.CoreFunctions.Sequences;

namespace Simp.Parsing
{
	/// <summary>Implementation of <see cref="IParser"/> using Oslo Textual DSL APIs.</summary>
	public class OsloParser : IParser
	{
		private readonly DynamicParser _grammarParser;

		/// <summary>Constructor.</summary>
		public OsloParser()
		{	//	Find the compiled grammar file resource
			string resourceName = string.Concat(
				GetType().Assembly.GetName().Name,
				".OsloComponents",
				".Simp.mx");

			using (Stream stream = GetType().Assembly.GetManifestResourceStream(resourceName))
			{
				MImage grammar = new MImage(stream, false);

				_grammarParser = grammar.ParserFactories["Simp.Dsl.Simp"].Create();
				_grammarParser.GraphBuilder = new NodeGraphBuilder();
			}
		}

		#region IParser Members
		IFunction[] IParser.Parse(string content)
		{
			if (string.IsNullOrEmpty(content))
			{
				throw new ArgumentNullException("content");
			}
			try
			{
				using (StringReader reader = new StringReader(content))
				using (TextReaderTextStream stream = new TextReaderTextStream(reader))
				{
					object parsingResult = _grammarParser.Parse(stream, null);

					if (parsingResult != null)
					{
						Node rootNode = (Node)parsingResult;
						List<IFunction> functionList = new List<IFunction>();
						var firstLevelCollection = rootNode.ViewAllNodes();

						foreach (var firstLevelNode in firstLevelCollection)
						{
							switch (GetNodeName(firstLevelNode))
							{
								case "FunctionDefinition":
									IFunction functionDefinition = LoadFunctionDefinition(firstLevelNode, false);

									functionList.Add(functionDefinition);
									break;
								case "Return":
									Node returnValueNode = firstLevelNode.ViewAllNodes().First();
									IFunction function = LoadFunction(returnValueNode);

									functionList.Add(function);
									break;
								default:
									throw new NotImplementedException();
							}
						}

						return functionList.ToArray();
					}
					else
					{
						throw new ParsingException();
					}
				}
			}
			catch (ParsingException)
			{
				throw;
			}
			catch (Exception ex)
			{
				throw new ParsingException("Error during parsing", ex);
			}
		}
		#endregion

		private static IFunction LoadFunction(Node functionNode)
		{
			switch (GetNodeName(functionNode))
			{
				case "FunctionCall":
					return LoadFunctionCall(functionNode);
				case IntegerFunction.IntegerFunctionName:
					return LoadInteger(functionNode.ViewAllNodes().First());
				case RealFunction.RealFunctionName:
					return LoadReal(functionNode.ViewAllNodes().First());
				case ExplicitSequenceFunction.ExplicitSequenceFunctionName:
					return LoadExplicitSequence(functionNode);
				case IntervalSequenceFunction.IntervalSequenceFunctionName:
					return LoadIntervalSequence(functionNode);
				case DerivedSequenceFunction.ExplicitSequenceFunctionName:
					return LoadDerivedSequence(functionNode);
				case RangeSelectorFunction.RangeSelectorFunctionName:
					return LoadRangeSelector(functionNode);
				case ElementSelectorFunction.ElementSelectorFunctionName:
					return LoadElementSelector(functionNode);
				case "Lambda":
					return LoadFunctionDefinition(functionNode, true);
				case "FunctionScope":
					return LoadFunctionScope(functionNode);
				default:
					throw new NotSupportedException(GetNodeName(functionNode));
			}
		}

		private static IFunction LoadFunctionScope(Node functionNode)
		{
			List<IFunction> definitionList = new List<IFunction>();
			IFunction returnFunction = null;

			foreach (Node node in functionNode.ViewAllNodes())
			{
				switch (GetNodeName(node))
				{
					case "FunctionDefinition":
						definitionList.Add(LoadFunctionDefinition(node, false));
						break;
					case "Return":
						returnFunction = LoadFunction(node.ViewAllNodes().First());
						break;
					default:
						throw new NotSupportedException(GetNodeName(node));
				}
			}

			ScopeFunction scopeFunction = new ScopeFunction(returnFunction, definitionList.ToArray());

			return scopeFunction;
		}

		private static IFunction LoadRangeSelector(Node functionNode)
		{
			IFunction source = null;
			IFunction first = null;
			IFunction last = null;

			foreach (Node node in functionNode.ViewAllNodes())
			{
				switch (GetNodeName(node))
				{
					case "Source":
						source = LoadFunction(node.ViewAllNodes().First());
						break;
					case "First":
						first = LoadFunction(node.ViewAllNodes().First());
						break;
					case "Last":
						last = LoadFunction(node.ViewAllNodes().First());
						break;
					default:
						throw new NotSupportedException(GetNodeName(node));
				}
			}

			return new BoundFunctionCalling(new RangeSelectorFunction(), source, first, last);
		}

		private static IFunction LoadElementSelector(Node functionNode)
		{
			IFunction source = null;
			IFunction index = null;

			foreach (Node node in functionNode.ViewAllNodes())
			{
				switch (GetNodeName(node))
				{
					case "Source":
						source = LoadFunction(node.ViewAllNodes().First());
						break;
					case "Index":
						index = LoadFunction(node.ViewAllNodes().First());
						break;
					default:
						throw new NotSupportedException(GetNodeName(node));
				}
			}

			return new BoundFunctionCalling(new ElementSelectorFunction(), source, index);
		}

		private static IFunction LoadDerivedSequence(Node functionNode)
		{
			IFunction source = null;
			IFunction transform = null;

			foreach (Node node in functionNode.ViewAllNodes())
			{
				switch (GetNodeName(node))
				{
					case "Source":
						source = LoadFunction(node.ViewAllNodes().First());
						break;
					case "Transform":
						transform = LoadFunction(node.ViewAllNodes().First());
						break;
					default:
						throw new NotSupportedException(GetNodeName(node));
				}
			}

			return new BoundFunctionCalling(new DerivedSequenceFunction(), source, transform);
		}

		private static IFunction LoadIntervalSequence(Node functionNode)
		{
			IFunction first = null;
			IFunction last = null;

			foreach (Node node in functionNode.ViewAllNodes())
			{
				switch (GetNodeName(node))
				{
					case "First":
						first = LoadFunction(node.ViewAllNodes().First());
						break;
					case "Last":
						last = LoadFunction(node.ViewAllNodes().First());
						break;
					default:
						throw new NotSupportedException(GetNodeName(node));
				}
			}

			return new BoundFunctionCalling(new IntervalSequenceFunction(), first, last);
		}

		private static IFunction LoadExplicitSequence(Node functionNode)
		{
			List<IFunction> subFunctionList = new List<IFunction>();

			foreach (Node subNode in functionNode.ViewAllNodes())
			{
				IFunction subFunction = LoadFunction(subNode);

				subFunctionList.Add(subFunction);
			}

			return new BoundFunctionCalling(new ExplicitSequenceFunction(subFunctionList.Count), subFunctionList.ToArray());
		}

		private static IFunction LoadFunctionDefinition(Node functionNode, bool isLambda)
		{
			string name = isLambda ? "Lambda" : null;
			IFunction body = null;
			string[] parameterNames = FunctionHelper.EmptyParameterNames;
			IFunctionParameterBinding[] defaultValues = FunctionHelper.EmptyBindings;

			foreach (Node node in functionNode.ViewAllNodes())
			{
				switch (GetNodeName(node))
				{
					case "Name":
						name = GetNodeValue(node);
						break;
					case "Args":
						LoadFunctionDefinitionParameters(node, out parameterNames, out defaultValues);
						break;
					case "Body":
						Node innerNode = node.ViewAllNodes().First();

						body = LoadFunction(innerNode);
						break;
					default:
						throw new NotSupportedException(GetNodeName(node));
				}
			}

			CustomFunction customFunction = new CustomFunction(body, name, parameterNames, defaultValues);

			return isLambda ? (IFunction)new FunctionReference(customFunction) : customFunction;
		}

		private static void LoadFunctionDefinitionParameters(
			Node argsNode,
			out string[] parameterNames,
			out IFunctionParameterBinding[] defaultValues)
		{
			List<string> parameterNameList = new List<string>();
			List<IFunctionParameterBinding> bindingList = new List<IFunctionParameterBinding>();

			foreach (Node parameterNode in argsNode.ViewAllNodes())
			{
				string parameterName = null;
				IFunction parameterDefaultValue = null;

				if (GetNodeName(parameterNode) != "ParameterDefinition")
				{
					throw new NotSupportedException(GetNodeName(parameterNode));
				}
				foreach (Node subNode in parameterNode.ViewAllNodes())
				{
					switch (GetNodeName(subNode))
					{
						case "Name":
							parameterName = GetNodeValue(subNode);
							break;
						case "Default":
							parameterDefaultValue = LoadFunction(subNode.ViewAllNodes().First());
							break;
						default:
							throw new NotSupportedException(GetNodeName(subNode));
					}
				}
				parameterNameList.Add(parameterName);
				if (parameterDefaultValue != null)
				{
					IFunctionParameterBinding binding =
						FunctionHelper.FunctionParameterBindingFactory.Build(parameterName, parameterDefaultValue);

					bindingList.Add(binding);
				}
			}
			parameterNames = parameterNameList.ToArray();
			defaultValues = bindingList.ToArray();
		}

		private static IFunction LoadFunctionCall(Node functionCallNode)
		{
			string functionName = null;
			IFunctionParameterBinding[] bindings = null;

			foreach (Node node in functionCallNode.ViewAllNodes())
			{
				switch (GetNodeName(node))
				{
					case "Name":
						functionName = GetNodeValue(node);
						break;
					case "Args":
						bindings = LoadFunctionCallParameters(node);
						break;
					default:
						throw new NotSupportedException();
				}
			}

			return new UnboundFunctionCalling(functionName, false, bindings == null ? FunctionHelper.EmptyBindings : bindings);
		}

		private static IFunctionParameterBinding[] LoadFunctionCallParameters(Node argsNode)
		{
			List<IFunctionParameterBinding> bindingList = new List<IFunctionParameterBinding>();

			foreach (Node parameterNode in argsNode.ViewAllNodes())
			{
				string parameterName = null;
				IFunction parameterValue = null;

				if (GetNodeName(parameterNode) != "Parameter")
				{
					throw new NotSupportedException(GetNodeName(parameterNode));
				}
				foreach (Node subNode in parameterNode.ViewAllNodes())
				{
					switch (GetNodeName(subNode))
					{
						case "Name":
							parameterName = GetNodeValue(subNode);
							break;
						case "Value":
							parameterValue = LoadFunction(subNode.ViewAllNodes().First());
							break;
						default:
							throw new NotSupportedException();
					}
				}
				IFunctionParameterBinding binding =
					FunctionHelper.FunctionParameterBindingFactory.Build(parameterName, parameterValue);

				bindingList.Add(binding);
			}

			return bindingList.ToArray();
		}

		private static IFunction LoadInteger(Node integerNode)
		{
			int intValue = int.Parse(GetNodeValue(integerNode));

			return new IntegerFunction(intValue);
		}

		private static IFunction LoadReal(Node realNode)
		{
			double doubleValue = double.Parse(GetNodeValue(realNode));

			return new RealFunction(doubleValue);
		}

		private static string GetNodeName(Node node)
		{
			if (node.NodeKind == NodeKind.Atomic)
			{
				DefaultGraphStoreConstantNode constantNode = (DefaultGraphStoreConstantNode)node.StoreKey;

				return (string)constantNode.Brand.Text;
			}
			else
			{
				return node.Brand.Text;
			}
		}

		private static string GetNodeValue(Node node)
		{
			if (node.NodeKind == NodeKind.Atomic)
			{
				return node.AtomicValue.ToString();
			}
			else
			{
				Node valueNode = node.ViewAllNodes().First();

				return valueNode.AtomicValue.ToString();
			}
		}
	}
}